#include "disk.h"
#include "../core/sampling.h"

Disk::Disk(const Transform *o2w, const Transform *w2o, bool ro, Reference<Material> mat, float rd, float ht) :
	Shape(o2w, w2o, ro, mat), radius(rd), height(ht) {
}

BBox Disk::ObjectBound() const {
	return BBox(Point3(-radius, -radius, height),Point3(radius, radius, height));
}

bool Disk::Intersect(const Ray &r, Intersection *isect) const {
	Ray ray;
	(*WorldToObject)(r, &ray);
	// Compute intersection for disk plane
	if (fabsf(ray.d.z) < EPSILON(7)) 
		return false;
	// t = (h - o_z) / ray.d.z
	float thit = (height - ray.o.z) * ray.dRcp.z;
	if(thit < ray.mint || thit > ray.maxt)
		return false;
	// See if hit point inside the disk
	Point3 phit = ray(thit);
	float dist2 = phit.x * phit.x + phit.y * phit.y;
	if(dist2 > radius * radius)
		return false;
	// A hit is found, update r.maxt and isect
	r.maxt = thit;
	isect->t = thit;
	isect->shape = this;
	return true;
}

bool Disk::IntersectP(const Ray &r) const {
	Ray ray;
	(*WorldToObject)(r, &ray);
	// Compute intersection for disk plane
	if (fabsf(ray.d.z) < EPSILON(7)) 
		return false;
	// t = (h - o_z) / ray.d.z
	float thit = (height - ray.o.z) * ray.dRcp.z;
	if(thit < ray.mint || thit > ray.maxt)
		return false;
	// See if hit point inside the disk
	Point3 phit = ray(thit);
	float dist2 = phit.x * phit.x + phit.y * phit.y;
	if(dist2 > radius * radius)
		return false;

	return true;
}

void Disk::FillIntersection(const Ray &r, Intersection *isect) const {
	isect->p = r(isect->t);
	Point3 phit = (*WorldToObject)(isect->p);
	float dist2 = phit.x * phit.x + phit.y * phit.y;
	float phi = atan2f(phit.y, phit.x);
	if (phi < 0) 
		phi += 2. * M_PI;
	// UV coordinate
	isect->uv.x = phi * INV_TWOPI;
	isect->uv.y = 1.f - sqrtf(max(0.f, 1.f - dist2)) / radius;
	// Dpdu Dpdv
	Vector3 dpdu(-phit.y * 2 * M_PI, phit.x * 2 * M_PI, 0.);
	Vector3 dpdv(-phit.x / (1-isect->uv.y), -phit.y / (1-isect->uv.y), 0.);
	// Transform to world space
	isect->dpdu = (*ObjectToWorld)(dpdu);
	isect->dpdv = (*ObjectToWorld)(dpdv);

	isect->dndu = (*ObjectToWorld)(Normal(0.,0.,0.));
	isect->dndv = (*ObjectToWorld)(Normal(0.,0.,0.));
	//
	isect->geometryFrame.n = Normalize((*ObjectToWorld)(Normal(0., 0., 1.0)));
	isect->geometryFrame.s = Normalize(isect->dpdu);
	isect->geometryFrame.t = Normalize(isect->dpdv);

	if(ReverseOrientation)
		isect->geometryFrame.n *= -1.f;
	
	isect->shadingFrame = isect->geometryFrame;

	isect->shape = this;
	isect->hasUVPartials = false;

	isect->objectToWorld = this->ObjectToWorld;
	isect->worldToObject = this->WorldToObject;

	isect->isLight = (this->arealight != NULL);
	if(isect->isLight) {
		//isect->le = this->arealight->GetLe();
		Warning("isect->le not initialized yet\n");
	}
}

float Disk::Area() const {
	return M_PI * radius * radius;
}

Point3 Disk::Sample(const Point2 &sample, Normal *Ns) const {
	Point2 p;
	p = ConcentricSamplingDisk(sample);
	Point3 ps(p.x * radius, p.y * radius, height);
	*Ns = Normalize((*ObjectToWorld)(Normal(0.0, 0.0, 1.0)));
	if(ReverseOrientation)
		*Ns *= -1.f;
	return (*ObjectToWorld)(ps);
}